package com.appspot.codelab.docs.editor.advanced.client;

import java.util.Date;
import java.util.HashMap;

import com.appspot.codelab.docs.client.commands.Command;
import com.appspot.codelab.docs.client.commands.CurrentDocumentChangedCommand;
import com.appspot.codelab.docs.client.commands.CurrentDocumentCloseCommand;
import com.appspot.codelab.docs.client.commands.CurrentDocumentCopyCommand;
import com.appspot.codelab.docs.client.commands.CurrentDocumentDeleteCommand;
import com.appspot.codelab.docs.client.commands.CurrentDocumentLoadContentsCommand;
import com.appspot.codelab.docs.client.commands.CurrentDocumentRefreshCommand;
import com.appspot.codelab.docs.client.commands.CurrentDocumentReloadCommand;
import com.appspot.codelab.docs.client.commands.CurrentDocumentRenameCommand;
import com.appspot.codelab.docs.client.commands.CurrentDocumentSaveAndCloseCommand;
import com.appspot.codelab.docs.client.commands.CurrentDocumentSaveCommand;
import com.appspot.codelab.docs.client.commands.NewDocumentLoadCommand;
import com.appspot.codelab.docs.client.commands.NewDocumentStartCommand;
import com.appspot.codelab.docs.client.commands.SystemListDirectoryCommand;
import com.appspot.codelab.docs.client.commands.SystemLoadDocumentCommand;
import com.appspot.codelab.docs.client.commands.SystemOpenDocumentCommand;
import com.appspot.codelab.docs.client.commands.SystemOpenPageCommand;
import com.appspot.codelab.docs.client.commands.SystemPasteCommand;
import com.appspot.codelab.docs.client.commands.SystemRedoCommand;
import com.appspot.codelab.docs.client.commands.SystemSetProjectDetailsCommand;
import com.appspot.codelab.docs.client.commands.SystemSetSyntaxParserCommand;
import com.appspot.codelab.docs.client.commands.SystemShowDialogCommand;
import com.appspot.codelab.docs.client.commands.SystemSignOutCommand;
import com.appspot.codelab.docs.client.commands.SystemToggleColorSyntaxCommand;
import com.appspot.codelab.docs.client.commands.SystemToggleFullScreenCommand;
import com.appspot.codelab.docs.client.commands.SystemToggleLineNumbersCommand;
import com.appspot.codelab.docs.client.commands.SystemToggleSpellcheckCommand;
import com.appspot.codelab.docs.client.commands.SystemToggleWrapTextCommand;
import com.appspot.codelab.docs.client.commands.SystemUndoCommand;
import com.appspot.codelab.docs.client.commands.SystemValidateProjectDetailsCommand;
import com.appspot.codelab.docs.client.content.dialogs.DynamicAboutDialog;
import com.appspot.codelab.docs.client.content.dialogs.DynamicFileListDialog;
import com.appspot.codelab.docs.client.content.dialogs.DynamicFileSelectionDialog;
import com.appspot.codelab.docs.client.content.dialogs.DynamicPasswordSelectionDialog;
import com.appspot.codelab.docs.client.content.dialogs.DynamicProjectSelectionDialog;
import com.appspot.codelab.docs.client.content.dialogs.DynamicUploadDialog;
import com.appspot.codelab.docs.client.content.dialogs.StaticActionDialog;
import com.appspot.codelab.docs.client.content.dialogs.StaticErrorDialog;
import com.appspot.codelab.docs.client.content.dialogs.StaticLoadingDialog;
import com.appspot.codelab.docs.client.content.dialogs.StaticActionDialog.ActionDialogOption;
import com.appspot.codelab.docs.client.content.icons.Icons;
import com.appspot.codelab.docs.client.events.AsyncInstantiationCallback;
import com.appspot.codelab.docs.client.events.CommandBus;
import com.appspot.codelab.docs.client.events.CommandEvent;
import com.appspot.codelab.docs.client.events.CommandHandler;
import com.appspot.codelab.docs.client.events.FileSelectionEvent;
import com.appspot.codelab.docs.client.events.FileSelectionHandler;
import com.appspot.codelab.docs.client.events.PasswordSelectionEvent;
import com.appspot.codelab.docs.client.events.PasswordSelectionHandler;
import com.appspot.codelab.docs.client.events.ProjectSelectionEvent;
import com.appspot.codelab.docs.client.events.ProjectSelectionHandler;
import com.appspot.codelab.docs.client.events.Scheduler;
import com.appspot.codelab.docs.client.repository.RepositoryAuthorizationException;
import com.appspot.codelab.docs.client.repository.RepositoryDirectory;
import com.appspot.codelab.docs.client.repository.RepositoryPath;
import com.appspot.codelab.docs.client.repository.RepositoryProject;
import com.appspot.codelab.docs.client.repository.RepositoryServiceClient;
import com.appspot.codelab.docs.client.repository.RepositoryUser;
import com.appspot.codelab.docs.client.repository.RepositoryDirectory.RepositoryFile;
import com.appspot.codelab.docs.editor.advanced.client.DocsAdvancedEditorView.LockFunction;
import com.appspot.codelab.docs.editor.advanced.client.parts.EditorPart.SyntaxHandler;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.RunAsyncCallback;
import com.google.gwt.event.dom.client.LoadEvent;
import com.google.gwt.event.dom.client.LoadHandler;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;

/**
 * The MVC controller for the advanced document editor.
 */
public class DocsAdvancedEditorController implements CommandHandler {

  protected static DocsAdvancedEditorController instance;

  /**
   * Retrieves the single instance of this class, using asynchronous instantiation.
   * 
   * @param cb the asynchronous instantiation callback
   */
  public static void get(final AsyncInstantiationCallback<DocsAdvancedEditorController> cb) {
	GWT.runAsync(new RunAsyncCallback() {
		@Override
		public void onFailure(Throwable reason) {
		  cb.onFailure(reason);
		}
		@Override
		public void onSuccess() {
	      if (instance == null) {
	        instance = new DocsAdvancedEditorController();
	      }
		  cb.onSuccess(instance);
		}
	});
  }
  
  private String currentRevision;
  private RepositoryPath currentFilePath;
  private SyntaxHandler currentParser = SyntaxHandler.Java;
  private RepositoryServiceClient repoService;
  private RepositoryFile currentDocument;
  private RepositoryProject currentProject;
  private HashMap<String, RepositoryDirectory> directoryIndex;
  private RepositoryUser currentUser;
  private Scheduler scheduler;
  private DocsAdvancedEditorView app;
  private boolean isSaving = false, isDirty = false, isStored = false;
  
  /**
   * Construct a new controller. Instantiate any required service end points.
   */
  protected DocsAdvancedEditorController() {
	repoService = new RepositoryServiceClient();
	directoryIndex = new HashMap<String, RepositoryDirectory>();
	CommandBus.get().addCommandHandler(this);
  }
  
  /**
   * Initialize by first checking authentication and retrieving the
   * logged on user.
   */
  public void initialize() {
    showStatus("Authenticating...", true);
    repoService.getUser(new AsyncCallback<RepositoryUser>() {
		@Override
		public void onFailure(Throwable caught) {
		  Window.alert("Authentication Failure: " + caught.getMessage());
		}
		@Override
		public void onSuccess(RepositoryUser result) {
		  clearStatus();
  		  if (result == null) {
  		    Window.alert("No login detected. Ensure that any requests go through the server side, " +
  		        "to enforce authentication, rather than directly to the HTML " +
  		        "content.");
  		  } else {
  		    currentUser = result;
            start();
  		  }
		}
    });
  }
  
  /**
   * Start the app by loading the UI, asynchronously and setting up
   * additional elements, such as the scheduler.
   * 
   * Load a document for editing.
   */
  public void start() {
	showStatus("Loading components...", true);
	DocsAdvancedEditorView.get(currentUser.getEmail(),
	    new AsyncInstantiationCallback<DocsAdvancedEditorView>() {
			@Override
			public void onFailure(Throwable caught) {
			  handleAsyncLoadError(caught);
			}
			@Override
			public void onSuccess(DocsAdvancedEditorView result) {
			  app = result;
			  clearStatus();
		      loadDocument();
		      scheduler = new Scheduler();
		      scheduler.addCommandHandler(DocsAdvancedEditorController.this);
			}
		}
	);
  }
  
  /**
   * Extracts the "docid" parameter from the URL querystring and loads the
   * respective document - the document details are loaded first, followed by
   * the document contents.
   * If no querystring parameter is present, check for a hash value to use
   * as the document id.
   * If "docid" is blank then a new, unsaved, document is loaded.
   */
  public void loadDocument() {
	//TODO: break this method into commands
    String resPath = Window.Location.getParameter("docpath");
    if (resPath == null || resPath.equals("")) {
      resPath = Window.Location.getHash();
      if (resPath != null && resPath.startsWith("#")) {
    	resPath = com.google.gwt.http.client.URL.decodeComponent(resPath.substring(1));
      }
    }
    if (resPath != null && !resPath.equals("")) {
      int c = resPath.indexOf("/");
      if (c > 0) {
    	String projectName = resPath.substring(0, c);
    	currentFilePath = new RepositoryPath(resPath.substring(c));
    	currentProject = new RepositoryProject();
    	currentProject.setProjectName(projectName);
    	repoService.getProject(projectName, new AsyncCallback<RepositoryProject>() {
			@Override
			public void onFailure(Throwable caught) {
			  //TODO: handle error
			}
			@Override
			public void onSuccess(RepositoryProject result) {
			  if (result != null) {
			    currentProject = result;
			  }
			}
    	});
        execute(new SystemLoadDocumentCommand(currentFilePath.getPath()));
      }
    } else {
      final Runnable selectFile = new Runnable() {
		@Override
		public void run() {
    	  DynamicFileSelectionDialog.get(
    		  currentProject.getProjectName(),
    		  new FileSelectionHandler() {
			  @Override
			  public void onSelection(FileSelectionEvent event) {
				currentFilePath = new RepositoryPath(event.getSelectedItem());
                execute(new SystemLoadDocumentCommand(currentFilePath.getPath()));
			  }
    	  }).center();
		}
      };
      if (currentProject == null) {
    	DynamicProjectSelectionDialog.get(currentUser.getEmail(),
    		new ProjectSelectionHandler() {
			@Override
			public void onSelection(ProjectSelectionEvent event) {
			  String project = event.getSelectedItem();
			  currentProject = new RepositoryProject();
			  currentProject.setProjectName(project);
			  //get previously stored project entry, if one is available
		      repoService.getProject(project, new AsyncCallback<RepositoryProject>() {
				  @Override
				  public void onFailure(Throwable caught) {
					//TODO: handle error
				  }
				  @Override
			      public void onSuccess(RepositoryProject result) {
					if (result != null) {
					  currentProject = result;
					}
		          }
		      });
			  selectFile.run();
			}
    	}).center();
      } else {
    	selectFile.run();
      }
    }
  }
  
  /**
   * Closes the current window.
   * 
   * @TODO: browsers seem to be phasing out support for closing windows,
   * rethink the use of features that close the current window
   */
  private native void close() /*-{
  	$wnd.open('', '_self', '');//for chrome
    $wnd.close();
  }-*/;
  

  /**
   * Sets the currently open document and updates the header info and window title
   * with the respective document information.
   * 
   * If no docid parameter is present, set the document id as the url hash - this
   * will update the url to become specific to the current document, without
   * causing a browser refresh.
   * 
   * @param doc the document which to use
   * @param requireNewEtag whether a new Etag is expected for the specified document
   */
  private void setDocument(RepositoryFile doc) {
	if (doc == null) return;
    currentDocument = doc;
    isStored = currentDocument.getRevisionLabel() != null;
    app.getHeader().setTitle(doc.getName());
    Window.setTitle(doc.getName() + " - Code Lab");
    if (isStored) {
      app.getHeader().setInfo(currentDocument.getRevisionLabel(),
          new Date(),
          currentDocument.getAuthorLabel());
      String urlDocId = Window.Location.getParameter("docpath");
      if (urlDocId == null || urlDocId.equals("")) {
        History.newItem(com.google.gwt.http.client.URL.encodeComponent(currentProject.getProjectName() + currentFilePath.getPath()));
      }
    }
  }
   
  /**
   * Shows a status message. If modal, a loading dialog is displayed, otherwise
   * the header status area is used.
   * 
   * @param message the status message to display
   * @param modal whether the status display is modal
   */
  private void showStatus(String message, boolean modal) {
    if (modal) {
      StaticLoadingDialog loadingDialog = StaticLoadingDialog.get();
      loadingDialog.setMessage(message);
      loadingDialog.center();
    } else {
      if (app != null) {
    	app.getHeader().setStatus(message);
      }
    }
  }
  
  /**
   * Clears and hides any visible status messages.
   */
  private void clearStatus() {
	StaticLoadingDialog.get().hide();
	if (app != null) {
      app.getHeader().setStatus("");
	}
  }
  
  /**
   * Handles a command error by displaying an error dialog.
   * 
   * @param error the error exception
   * @param command the command that triggered the error
   * @param alternate the command to trigger on cancel
   * @param retryAttempts the number of times to retry the command before displaying an error dialog
   */
  private void handleError(final Throwable error, final Command command,
	  final Command alternate, int retryAttempts){
	//TODO: move out password selection logic
    clearStatus();
    if (command.getAttemptCount() <= retryAttempts) {
      command.newAttempt();
      execute(command);
    } else {
      if (error instanceof RepositoryAuthorizationException) {
    	DynamicPasswordSelectionDialog.get(
    		currentProject.getProjectName(),
    	    currentUser.getEmail(),
    	    new PasswordSelectionHandler() {
			  @Override
			  public void onSelection(PasswordSelectionEvent event) {
				currentProject.setProjectToken(event.getSelectedItem());
				if (event.isRememberValue()) {
				  repoService.putProject(
				      currentProject.getProjectName(),
				      currentProject.getProjectToken(),
				      new AsyncCallback<RepositoryProject>() {
						@Override
						public void onFailure(Throwable caught) {
						  //TODO: handler failure
						}
						@Override
						public void onSuccess(RepositoryProject result) {
						  currentProject = result;
						}
				      });
				}
                command.newAttempt();
                execute(command);
			  }
    	}).center();
      } else {
        StaticErrorDialog errorDialog = StaticErrorDialog.get();
        errorDialog.update(error, command, alternate);
        errorDialog.center();
      }
    }
  }
  
  /**
   * Handles an async load error by displaying an alert box.
   * 
   * @param caught the error exception
   */
  private void handleAsyncLoadError(Throwable caught) {
	Window.alert("Error loading a required component.");
  }
  
  /************************************************************************************
   *************************** Begin Controller Logic *********************************
   ************************************************************************************/

  private void execute(final NewDocumentStartCommand cmd) {
    Date now = new Date();
    StaticActionDialog ad = StaticActionDialog.get();
    ad.update("New Document", "Do you want to start the new document in a new window?",
        new ActionDialogOption[] {
    	  new ActionDialogOption("Open New Window", new SystemOpenPageCommand("Untitled" + now.getTime(), "/docs", false)),
    	  new ActionDialogOption("Use Current Window", new SystemOpenPageCommand("Untitled" + now.getTime(), "/docs", true))
    	}
    );
    ad.center();
  }
  private void execute(final NewDocumentLoadCommand cmd) {
	repoService.getNewSourceFile(currentUser, new AsyncCallback<RepositoryFile>() {
		@Override
		public void onFailure(Throwable caught) {
          handleError(caught, cmd, null, 1);
		}
		@Override
		public void onSuccess(RepositoryFile result) {
	      setDocument(result);
	    }
	});
  }
  private void execute(final CurrentDocumentCopyCommand cmd) {
    showStatus("Copying document...", true);
    final String newName = Window.prompt("Enter new document name:", "Copy Of " + currentDocument.getName());
    if (newName != null && !newName.equals("")) {
      final String newPath = currentFilePath.getDirectoryPath() + newName;
	  repoService.uploadSourceFile(
		currentProject.getProjectName(),
		newPath,
	    currentUser.getEmail(),
	    currentProject.getProjectToken(),
	    app.getEditor().getText(),
	    new AsyncCallback<RepositoryFile>() {
			@Override
			public void onFailure(Throwable caught) {
              handleError(caught, cmd, null, 0);
			}
			@Override
			public void onSuccess(RepositoryFile result) {
              clearStatus();
              String url = "/docs?docpath=" + com.google.gwt.http.client.URL.encodeComponent(newPath);
              StaticActionDialog actionDialog = StaticActionDialog.get();
			  actionDialog.update("Open Copied Document",
			      "A copy of the current document has been created and is available for editing.",
			      new ActionDialogOption[] {
					  new ActionDialogOption("Open Copied Document",
					      new SystemOpenPageCommand(String.valueOf(new Date().getTime()), url, false))
				  }
			  );
			  actionDialog.center();
			}
	  });
    }
  }
  private void execute(final CurrentDocumentSaveCommand cmd) {
	if (currentDocument != null && isSaving) {
	  return;
	}
	if (cmd.isScheduled()) {
	  if (currentDocument == null ||
	      !isStored ||
	      !isDirty) {
	    return;
	  }
	}
	setFunctionLock(LockFunction.SAVE, true);
	repoService.uploadSourceFile(
		currentProject.getProjectName(),
		currentFilePath.getPath(),
	    currentUser.getEmail(),
	    currentProject.getProjectToken(),
	    app.getEditor().getText(),
	    new AsyncCallback<RepositoryFile>() {
			@Override
			public void onFailure(Throwable caught) {
		      setFunctionLock(LockFunction.SAVE, false);
              if (!cmd.isScheduled()) {
    	        handleError(caught, cmd, null, 0);
              }
			}
			@Override
			public void onSuccess(RepositoryFile result) {
				
		      setFunctionLock(LockFunction.SAVE, false);
              setDocument(result);
              isDirty = false;
			}
	});
  }
  private void execute(final CurrentDocumentSaveAndCloseCommand cmd) {
	//TODO: translate
	/*
    showStatus("Saving document...", true);
    docService.saveDocument(currentDocument.getDocumentId(),
    		"*",
    		currentDocument.getTitle(),
    	app.getEditor().getText(), new AsyncCallback<DocumentServiceEntry>() {
      public void onFailure(Throwable caught) {
        handleError(caught, cmd, null, 0);
      }
      public void onSuccess(DocumentServiceEntry result) {
        setDocument(result, false);
        clearStatus();
        close();
      }
    });
    */
  }
  private void execute(final CurrentDocumentRefreshCommand cmd) {
	//TODO: translate
	/*
    if (!cmd.isExecuteInBackground()) {
	  showStatus("Obtaining document metadata...", true);
    }
    docService.getDocument(currentDocument.getDocumentId(), new AsyncCallback<DocumentServiceEntry>() {
      public void onFailure(Throwable caught) {
    	if (!cmd.isExecuteInBackground()) {
          handleError(caught, cmd, null, 1);
    	}
      }
      public void onSuccess(DocumentServiceEntry result) {
        setDocument(result, false);
        if (!cmd.isExecuteInBackground()) {
          clearStatus();
        }
        if (cmd.getContinueCommand() != null) {
          execute(cmd.getContinueCommand(), DocsAdvancedEditorController.this);
        }
      }
    });
    */
  }
  private void execute(final CurrentDocumentReloadCommand cmd) {
	//TODO: translate
	/*
	showStatus("Reloading document...", true);
    Window.Location.assign("/docs?docid=" + currentDocument.getDocumentId() + "&hl=en");
    */
  }
  private void execute(final CurrentDocumentCloseCommand cmd) {
	close();
  }
  private void execute(final CurrentDocumentRenameCommand cmd) {
    final String newName = Window.prompt("Enter new document name:", currentDocument.getName());
    if (newName != null && !newName.equals("")) {
      RepositoryPath oldPath = currentFilePath;
      currentFilePath = new RepositoryPath(currentFilePath.getDirectoryPath() + newName);
      if (isStored) {
        showStatus("Renaming...", false);
        repoService.renameSourceFile(
        	currentProject.getProjectName(),
        	oldPath.getPath(),
            currentFilePath.getPath(),
            currentUser.getEmail(),
            currentProject.getProjectToken(),
            new AsyncCallback<RepositoryFile>() {
				@Override
				public void onFailure(Throwable caught) {
    	          handleError(caught, cmd, null, 0);
				}
				@Override
				public void onSuccess(RepositoryFile result) {
		          clearStatus();
				  setDocument(result);
				  app.getExplorer().refresh(currentFilePath.getDirectoryPath());
				}
        	});
      }
    }
  }
  private void execute(final CurrentDocumentDeleteCommand cmd) {
    if (Window.confirm("This document will be deleted and closed.")) {
      if (isStored) {
        showStatus("Deleting document...", true);
        repoService.deleteSourceFile(
            currentProject.getProjectName(),
            currentFilePath.getPath(),
            currentUser.getEmail(),
            currentProject.getProjectToken(),
            new AsyncCallback<Boolean>() {
          public void onFailure(Throwable caught) {
            handleError(caught, cmd, null, 0);
          }
          public void onSuccess(Boolean result) {
            clearStatus();
            close();
          }
        });
      } else {
        close();
      }
    }
  }
  private void execute(final CurrentDocumentLoadContentsCommand cmd) {
    showStatus("Loading document contents...", true);
    repoService.getSourceFileContents(
    	currentProject.getProjectName(),
        currentFilePath.getPath(),
        currentRevision,
    	new AsyncCallback<String>() {
			@Override
			public void onFailure(Throwable caught) {
              handleError(caught, cmd, null, 1);
			}
			@Override
			public void onSuccess(String result) {
              clearStatus();
              loadEditor(result);
			}
    });
  }
  private void execute(final SystemListDirectoryCommand cmd) {
	if (cmd.isUseCache() && directoryIndex.containsKey(cmd.getDirectory())) {
      cmd.getCallback().onSuccess(directoryIndex.get(cmd.getDirectory()));
	} else {
	  repoService.getSourceDirectory(
		  currentProject.getProjectName(),
		  cmd.getDirectory(),
	      currentRevision,
	      new AsyncCallback<RepositoryDirectory>() {
			@Override
			public void onFailure(Throwable caught) {
              handleError(caught, cmd, null, 1);
			}
			@Override
			public void onSuccess(RepositoryDirectory result) {
			  directoryIndex.put(result.getName(), result);
              cmd.getCallback().onSuccess(result);
			}
	  });
	}
  }
  private void execute(final SystemOpenDocumentCommand cmd) {
    Window.open("http://docs.google.com/Doc?docid=" + cmd.getDocumentId() + "&hl=en", cmd.getDocumentId(), "");
  }
  private void execute(final SystemShowDialogCommand cmd) {
	Class<?> type = cmd.getDialogType();
	if (type == DynamicAboutDialog.class) {
	  DynamicAboutDialog.get().center();
	} else if (type == DynamicFileListDialog.class) {
	  DynamicFileListDialog.get(currentProject.getProjectName()).center();
	} else if (type == DynamicUploadDialog.class) {
	  DynamicUploadDialog.get().center();
	}
  }
  private void execute(final SystemLoadDocumentCommand cmd) {
    showStatus("Loading document...", true);
    app.getExplorer().init(currentProject.getProjectName(), currentFilePath.getPath());
    repoService.getSourceFile(currentProject.getProjectName(), currentFilePath.getPath(),
        currentRevision,
        new AsyncCallback<RepositoryFile>() {
			@Override
			public void onFailure(Throwable caught) {
              handleError(caught, cmd, new NewDocumentLoadCommand(), 1);
			}
			@Override
			public void onSuccess(RepositoryFile result) {
	    	  if (result == null) {
	    	    handleError(new Exception("No document found with the ID " + cmd.getDocumentId()), cmd,
	    	        new NewDocumentLoadCommand(), 0);
	    	  } else {
	            setDocument(result);
	            clearStatus();
	            execute(new CurrentDocumentLoadContentsCommand());
	    	  }
			}
    });
  }
  private void execute(final SystemToggleFullScreenCommand cmd) {
    app.toggleFullScreen();
  }
  private void execute(final SystemToggleColorSyntaxCommand cmd) {
	String[] path = new String[] { "View", "Highlight Syntax" };
	if (app.getEditor().getColorSyntax()) {
	  app.getMenu().setMenuItemIcon(path, Icons.editorIcons.Blank());
	  app.getEditor().setColorSyntax(null);
	} else {
	  app.getMenu().setMenuItemIcon(path, Icons.editorIcons.CheckBlack());
	  app.getEditor().setColorSyntax(currentParser);
	}
  }
  private void execute(final SystemToggleLineNumbersCommand cmd) {
	String[] path = new String[] { "View", "Show Line Numbers" };
  	if (app.getEditor().getShowLineNumbers()) {
  	  app.getMenu().setMenuItemIcon(path, Icons.editorIcons.Blank());
  	  app.getEditor().setShowLineNumbers(false);
  	} else {
  	  app.getMenu().setMenuItemIcon(path, Icons.editorIcons.CheckBlack());
  	  app.getEditor().setShowLineNumbers(true);
  	}
  }
  private void execute(final SystemToggleWrapTextCommand cmd) {
	String[] path = new String[] { "View", "Wrap Text" };
	if (app.getEditor().getWrapText()) {
	  app.getMenu().setMenuItemIcon(path, Icons.editorIcons.Blank());
	  app.getEditor().setWrapText(false);
	} else {
	  app.getMenu().setMenuItemIcon(path, Icons.editorIcons.CheckBlack());
	  app.getEditor().setWrapText(true);
	}
  }
  private void execute(final SystemToggleSpellcheckCommand cmd) {
	String[] path = new String[] { "View", "Check Spelling" };
	if (app.getEditor().getUseSpellChecker()) {
	  app.getMenu().setMenuItemIcon(path, Icons.editorIcons.Blank());
	  app.getEditor().setUseSpellChecker(false);
	} else {
	  app.getMenu().setMenuItemIcon(path, Icons.editorIcons.CheckBlack());
	  app.getEditor().setUseSpellChecker(true);
	}
  }
  private void execute(final SystemSignOutCommand cmd) {
    showStatus("Signing out...", true);
    repoService.logout(new AsyncCallback<String>() {
		@Override
		public void onFailure(Throwable caught) {
          handleError(caught, cmd, null, 1);
		}
		@Override
		public void onSuccess(String result) {
          clearStatus();
          Window.Location.replace(result);
		}
    });
  }
  private void execute(final SystemPasteCommand cmd) {
	String pasteText = cmd.getText();
	String selText = app.getEditor().getSelectedText();
	if (selText != null) {
	  pasteText = pasteText.replace("<text here>", selText);
	}
	app.getEditor().replaceSelection(pasteText);
  }
  private void execute(final SystemUndoCommand cmd) {
  	app.getEditor().undo();
  }
  private void execute(final SystemRedoCommand cmd) {
  	app.getEditor().redo();
  }
  private void execute(final SystemOpenPageCommand cmd) {
	if (cmd.isUseCurrentWindow()) {
	  Window.Location.assign(cmd.getUrl());
	} else {
	  Window.open(cmd.getUrl(), cmd.getName(), "");
	}
  }
  private void execute(final SystemSetSyntaxParserCommand cmd) {
	app.getMenu().setMenuItemIcon(new String[] { "Syntax", currentParser.name() },
	    Icons.editorIcons.Blank());
	app.getMenu().setMenuItemIcon(new String[] { "Syntax", cmd.getParser().name() },
	    Icons.editorIcons.CheckBlack());
	currentParser = cmd.getParser();
	app.getEditor().setColorSyntax(currentParser);
  }
  private void execute(final SystemValidateProjectDetailsCommand cmd) {
	showStatus("Opening project...", true);
	repoService.projectExists(cmd.getProjectName(), new AsyncCallback<Boolean>() {
		@Override
		public void onFailure(Throwable caught) {
		  clearStatus();
		  cmd.getCallback().onFailure(caught);
		}
		@Override
		public void onSuccess(Boolean result) {
		  clearStatus();
		  cmd.getCallback().onSuccess(result);
		}
	});
  }
  private void execute(final SystemSetProjectDetailsCommand cmd) {
	currentProject = new RepositoryProject();
	currentProject.setProjectName(cmd.getProjectName());
  	repoService.getProject(cmd.getProjectName(), new AsyncCallback<RepositoryProject>() {
		@Override
		public void onFailure(Throwable caught) {
		  handleError(caught, cmd, null, 0);
		}
		@Override
		public void onSuccess(RepositoryProject result) {
		  if (result != null) {
		    currentProject = result;
		  }
		}
	});
  }
  private void execute(final CurrentDocumentChangedCommand cmd) {
	//TODO: translate
	/*
    if (currentDocument != null) {
	  currentDocument.setDirty(true);
	}
    */
  }
  private void execute(final Command cmd) {
    switch (cmd.getCommandId()) {
      case SystemListDirectoryCommand.serialUid: execute((SystemListDirectoryCommand) cmd); break;
      case SystemOpenDocumentCommand.serialUid: execute((SystemOpenDocumentCommand) cmd); break;
      case NewDocumentStartCommand.serialUid: execute((NewDocumentStartCommand) cmd); break;
      case CurrentDocumentChangedCommand.serialUid: execute((CurrentDocumentChangedCommand) cmd); break;
      case CurrentDocumentCopyCommand.serialUid: execute((CurrentDocumentCopyCommand) cmd); break;
      case CurrentDocumentSaveCommand.serialUid: execute((CurrentDocumentSaveCommand) cmd); break;
      case CurrentDocumentSaveAndCloseCommand.serialUid: execute((CurrentDocumentSaveAndCloseCommand) cmd); break;
      case CurrentDocumentRefreshCommand.serialUid: execute((CurrentDocumentRefreshCommand) cmd); break;
      case CurrentDocumentReloadCommand.serialUid: execute((CurrentDocumentReloadCommand) cmd); break;
      case CurrentDocumentCloseCommand.serialUid: execute((CurrentDocumentCloseCommand) cmd); break;
      case CurrentDocumentRenameCommand.serialUid: execute((CurrentDocumentRenameCommand) cmd); break;
      case CurrentDocumentDeleteCommand.serialUid: execute((CurrentDocumentDeleteCommand) cmd); break;
      case CurrentDocumentLoadContentsCommand.serialUid: execute((CurrentDocumentLoadContentsCommand) cmd); break;
      case NewDocumentLoadCommand.serialUid: execute((NewDocumentLoadCommand) cmd); break;
      case SystemLoadDocumentCommand.serialUid: execute((SystemLoadDocumentCommand) cmd); break;
      case SystemShowDialogCommand.serialUid: execute((SystemShowDialogCommand) cmd); break;
      case SystemToggleFullScreenCommand.serialUid: execute((SystemToggleFullScreenCommand) cmd); break;
      case SystemToggleColorSyntaxCommand.serialUid: execute((SystemToggleColorSyntaxCommand) cmd); break;
      case SystemToggleLineNumbersCommand.serialUid: execute((SystemToggleLineNumbersCommand) cmd); break;
      case SystemToggleWrapTextCommand.serialUid: execute((SystemToggleWrapTextCommand) cmd); break;
      case SystemToggleSpellcheckCommand.serialUid: execute((SystemToggleSpellcheckCommand) cmd); break;
      case SystemSignOutCommand.serialUid: execute((SystemSignOutCommand) cmd); break;
      case SystemPasteCommand.serialUid: execute((SystemPasteCommand) cmd); break;
      case SystemUndoCommand.serialUid: execute((SystemUndoCommand) cmd); break;
      case SystemRedoCommand.serialUid: execute((SystemRedoCommand) cmd); break;
      case SystemOpenPageCommand.serialUid: execute((SystemOpenPageCommand) cmd); break;
      case SystemSetSyntaxParserCommand.serialUid: execute((SystemSetSyntaxParserCommand) cmd); break;
      case SystemSetProjectDetailsCommand.serialUid: execute((SystemSetProjectDetailsCommand) cmd); break;
      case SystemValidateProjectDetailsCommand.serialUid: execute((SystemValidateProjectDetailsCommand) cmd); break;
      default:
        Window.alert("Not implemented");
        break;
    }
  }
  
  /************************************************************************************
   ***************************** End Controller Logic *********************************
   ************************************************************************************/
  
  /**
   * Handle a command event by executing the triggered command.
   * 
   * @param e the command event
   */
  @Override
  public void onCommand(CommandEvent e) {
	execute(e.getCommand());
  }
  
  /**
   * Loads the advanced text editor (a CodeMirror instance).
   * @TODO: currently, if dev mode, a dev dialog is displayed.
   * This should be moved elsewhere
   * 
   * @param value the text value to populate into the editor
   */
  private void loadEditor(final String value) {
    showStatus("Loading advanced editor...", true);
    app.loadEditor(
    	currentParser,
        true,
        true,
        false,
        new LoadHandler() {
            @Override
    	    public void onLoad(LoadEvent event) {
              app.getEditor().setText(value);
              if (currentDocument != null) {
            	isDirty = false;
              }
              clearStatus();
    	    }
        }
    );
  }

  /**
   * Locks or unlocks the UI to disable or enable a given feature.
   * 
   * @param func the function to lock or unlock
   * @param locked whether to lock or unlock the function
   */
  private void setFunctionLock(LockFunction func, boolean locked) {
	switch (func) {
	case SAVE:
	  isSaving = locked;
	  break;
	}
	app.setFunctionLock(func, locked);
  }
  
}
